import {
  Box,
  DragAndDropProvider,
  DraggableItem,
  DragHandle,
  DroppableContainer,
  FieldComponent,
  FieldEffect,
  Icon,
  PopoverConfirm,
  RefProp,
  useCall,
  useCreateCall,
  usePubSub,
  usePubSubState,
  useSetTimeout,
  useSimpleMemo,
  useTableActions,
  useTableColumns,
} from '@/components';
import { APP_COLUMN_KEYS, TABLE_KEYS } from '@/constants/componentKeys';
import { forkHandler, isNull, pick, simpleMerge, uid } from '@/utils';
import { Button, Form, Table } from 'antd';
import * as PropTypes from 'prop-types';
import React, {
  createContext,
  forwardRef,
  useContext,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react';
import { concatName, FormProvider } from './shared';

const mapFieldColumn = (col, colIndex, createCall) => {
  const [column, item] = pick(col, APP_COLUMN_KEYS);
  return {
    title: item.label,
    key: item.key,
    ...column,
    render: createCall((col, row, index) => (
      <FieldComponent
        {...item}
        name={concatName(index, item.name)}
        label={null}
        labelCol={{ span: 0 }}
        wrapperCol={{ span: 24 }}
        index={index}
      />
    )),
  };
};

const useFieldColumns = (fields) => {
  const createCall = useCreateCall();

  return useTableColumns(fields.map((col, index) => mapFieldColumn(col, index, createCall)));
};

const Context = createContext({});

function Provider({ children, ...rest }) {
  return <Context.Provider value={useSimpleMemo(rest)} children={children} />;
}

FieldTable = forwardRef(FieldTable);

const removeBtnCol = {
  key: 'removeBtn',
  width: '48px',
  align: 'center',
  render(col, row, index) {
    const { remove, onRemove } = useContext(Context);
    const handleRemove = useCall(forkHandler(onRemove, () => remove(index), true));
    return (
      <PopoverConfirm onOk={handleRemove} content='确认删除吗?'>
        <Button size='small' type='text' icon={<Icon type='delete' />} />
      </PopoverConfirm>
    );
  },
};

function RenderNextTick({ render }) {
  const [show, setShow] = useState(false);
  const setTimeout = useSetTimeout();
  useLayoutEffect(() => {
    setTimeout(setShow, 0, true);
  }, []);

  return show && render();
}

function Row({ index, onRow, row, ...props }) {
  const { pubsub, name } = useContext(Context);
  const $item = usePubSubItem(pubsub, index);

  if (isNull(index)) return <tr {...props} />;

  return (
    <RenderNextTick
      render={() => {
        const item = $item || pubsub.value?.[index];
        const rest = onRow?.(item, index) || null;
        return <DraggableItem row={item} index={index} {...props} {...rest} />;
      }}
    />
  );
}

function usePubSubItem(pubsub, index) {
  const state = usePubSubState(pubsub);
  const [value, setValue] = useState(state?.[index]);
  useEffect(() => {
    setValue(state?.[index] ?? null);
  }, [state, index]);

  return value;
}

const dragBtnCol = (show, pubsub) => ({
  key: 'dragHandle',
  title: '排序',
  width: '48px',
  align: 'center',
  render(col, row, index) {
    return (
      <RenderNextTick
        render={() => {
          const item = pubsub.value?.[index];
          if (show === false) return null;
          if (typeof show === 'function' && !show(item, index)) return null;

          return (
            <DragHandle>
              <Button size='small' type='text'>
                <Icon type='menu' />
              </Button>
            </DragHandle>
          );
        }}
      />
    );
  },
});

function FieldTable(props, ref) {
  const {
    name = 'list',
    showRemoveBtn = false,
    showAddBtn = false,
    showDragBtn = false,
    onAdd,
    onRemove,
    onRow,
    form,
    actions,
    actionCol,
    actionTitle,
    fields,
    scrollY = 'max(240px, min(720px, 40vh))',
    draggable = null,
    rowKey = 'id',
    ...rest
  } = props;

  // tableProps

  const meRef = useRef();
  const pubsub = usePubSub();
  const handleAdd = useCall(() => meRef.current?.add?.(onAdd?.(meRef.current)));

  const mergeProp = useRef({
    components: { body: { row: Row } },
    scroll: { y: scrollY },
    onRow: (row, index) => ({ index, row, onRow }),
  }).current;
  const tableProps = useSimpleMemo(simpleMerge(mergeProp, pick(rest, TABLE_KEYS)[0]));

  // columns
  let cols = [].concat(useFieldColumns(fields), useTableActions(actions, rest, actionCol, actionTitle));
  const dragCol = useMemo(() => (showDragBtn ? dragBtnCol(showDragBtn, pubsub) : null), [showDragBtn]);
  if (dragCol) cols.unshift(dragCol);
  if (showRemoveBtn) cols.unshift(removeBtnCol);
  const columns = useSimpleMemo(cols);

  // DnD provider config
  const getList = () => pubsub.value;
  const config = useMemo(() => {
    return {
      rowKey,
      itemComponent: 'tr',
      itemDraggingClassName: 'ant-table-row-selected',
      onDrop(rowCtx, currentIndex) {
        console.log('on drop', rowCtx.index, currentIndex);
        const beforeIndex = rowCtx.renderIndex;
        const afterIndex = rowCtx.renderIndex - rowCtx.index + currentIndex;
        meRef.current?.move(beforeIndex, afterIndex);
      },
      getDroppableKeys() {
        let list = getList();
        const isDraggable = typeof showDragBtn === 'function' ? showDragBtn : () => showDragBtn;
        return list.flatMap((item, index) => (isDraggable(item, index) ? [item[rowKey]] : []));
      },
      ...draggable,
    };
  }, [showDragBtn, draggable]);

  return (
    <>
      {showAddBtn && (
        <Box>
          <Button type='primary' onClick={handleAdd}>
            新增
          </Button>
          <Box py={1} />
        </Box>
      )}
      <FieldEffect name={name} pubsub={pubsub} />
      <FormProvider labelSpan={0} wrapperSpan={24} form={form} {...rest}>
        <Form.List name={name}>
          {(list, control) => (
            <>
              <RefProp ref={ref} refKey list={list} {...control} />
              <RefProp ref={meRef} refKey list={list} {...control} />
              {list.map((i) => (
                <FieldEffect key={i.key} noStyle name={concatName(i.name, rowKey)} initialValue={uid()} />
              ))}
              <Provider list={list} onRemove={onRemove} pubsub={pubsub} name={name} {...control}>
                <DragAndDropProvider {...config}>
                  <DroppableContainer>
                    <RenderNextTick
                      render={() => (
                        <Table
                          size='middle'
                          pagination={false}
                          {...tableProps}
                          rowKey='key'
                          columns={columns}
                          dataSource={list}
                        />
                      )}
                    />
                  </DroppableContainer>
                </DragAndDropProvider>
              </Provider>
            </>
          )}
        </Form.List>
      </FormProvider>
    </>
  );
}

FieldTable.propTypes = {
  /** antd form 实例 */
  form: PropTypes.object.isRequired,

  /** Form.List name 默认值 'list' */
  name: PropTypes.any,

  /** 是否显示每行的删除按钮 */
  showRemoveBtn: PropTypes.bool,

  /** 是否显示新增按钮 */
  showAddBtn: PropTypes.bool,

  /** 是否显示排序按钮 */
  showDragBtn: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),

  /** 获得新增默认值 */
  onAdd: PropTypes.func,

  fields: PropTypes.array,
  actions: PropTypes.object,
  actionCol: PropTypes.object,
  actionTitle: PropTypes.node,

  /** Table scroll.y */
  scrollY: PropTypes.any,
};

export default FieldTable;
